#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>

#include "ILexer.h"
#include "Scintilla.h"
#include "SciLexer.h"

#include "WordList.h"
#include "LexAccessor.h"
#include "Accessor.h"
#include "StyleContext.h"
#include "CharacterSet.h"
#include "LexerModule.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif


static inline bool IsAWordChar( const unsigned int ch ) {
  return ch >= 0x80 || isalnum( ch ) || ch == '-' || ch == '_';
}

inline bool IsCssOperator( const int ch ) {
  if( !( ( ch < 0x80 ) && isalnum( ch ) ) &&
      ( ch == '{' || ch == '}' || ch == ':' || ch == ',' || ch == ';' ||
        ch == '.' || ch == '#' || ch == '!' || ch == '@' ||
        ch == '*' || ch == '>' || ch == '+' || ch == '=' || ch == '~' || ch == '|' ||
        ch == '[' || ch == ']' || ch == '(' || ch == ')' ) ) {
    return true;
  }
  return false;
}


inline int NestingLevelLookBehind( unsigned int startPos, Accessor &styler ) {
  int ch;
  int nestingLevel = 0;
  for( unsigned int i = 0; i < startPos; i++ ) {
    ch = styler.SafeGetCharAt( i );
    if( ch == '{' ) {
      nestingLevel++;
    } else if( ch == '}' ) {
      nestingLevel--;
    }
  }
  return nestingLevel;
}

static void ColouriseCssDoc( unsigned int startPos, int length, int initStyle, WordList *keywordlists[], Accessor &styler ) {
  WordList &css1Props = *keywordlists[0];
  WordList &pseudoClasses = *keywordlists[1];
  WordList &css2Props = *keywordlists[2];
  WordList &css3Props = *keywordlists[3];
  WordList &pseudoElements = *keywordlists[4];
  WordList &exProps = *keywordlists[5];
  WordList &exPseudoClasses = *keywordlists[6];
  WordList &exPseudoElements = *keywordlists[7];
  StyleContext sc( startPos, length, initStyle, styler );
  int lastState = -1;
  int lastStateC = -1;
  int lastStateS = -1;
  int lastStateVar = -1;
  int lastStateVal = -1;
  int op = ' ';
  int opPrev = ' ';
  bool insideParentheses = false;
  bool isScssDocument = styler.GetPropertyInt( "lexer.css.scss.language" ) != 0;
  bool isLessDocument = styler.GetPropertyInt( "lexer.css.less.language" ) != 0;
  bool isHssDocument = styler.GetPropertyInt( "lexer.css.hss.language" ) != 0;
  bool hasVariables = isScssDocument || isLessDocument || isHssDocument;
  char varPrefix = 0;
  if( hasVariables ) {
    varPrefix = isLessDocument ? '@' : '$';
  }
  typedef enum _CommentModes { eCommentBlock = 0, eCommentLine = 1} CommentMode;
  CommentMode comment_mode = eCommentBlock;
  bool hasSingleLineComments = isScssDocument || isLessDocument || isHssDocument;
  bool hasNesting = false;
  int nestingLevel = 0;
  if( isScssDocument || isLessDocument || isHssDocument ) {
    hasNesting = true;
    nestingLevel = NestingLevelLookBehind( startPos, styler );
  }
  for( ; sc.More(); sc.Forward() ) {
    if( sc.state == SCE_CSS_COMMENT && ( ( comment_mode == eCommentBlock && sc.Match( '*', '/' ) ) || ( comment_mode == eCommentLine && sc.atLineEnd ) ) ) {
      if( lastStateC == -1 ) {
        unsigned int i = startPos;
        for( ; i > 0; i-- ) {
          if( ( lastStateC = styler.StyleAt( i - 1 ) ) != SCE_CSS_COMMENT ) {
            if( lastStateC == SCE_CSS_OPERATOR ) {
              op = styler.SafeGetCharAt( i - 1 );
              opPrev = styler.SafeGetCharAt( i - 2 );
              while( --i ) {
                lastState = styler.StyleAt( i - 1 );
                if( lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT )
                { break; }
              }
              if( i == 0 )
              { lastState = SCE_CSS_DEFAULT; }
            }
            break;
          }
        }
        if( i == 0 ) {
          lastStateC = SCE_CSS_DEFAULT;
        }
      }
      if( comment_mode == eCommentBlock ) {
        sc.Forward();
        sc.ForwardSetState( lastStateC );
      } else
      { sc.SetState( lastStateC ); }
    }
    if( sc.state == SCE_CSS_COMMENT ) {
      continue;
    }
    if( sc.state == SCE_CSS_DOUBLESTRING || sc.state == SCE_CSS_SINGLESTRING ) {
      if( sc.ch != ( sc.state == SCE_CSS_DOUBLESTRING ? '\"' : '\'' ) ) {
        continue;
      }
      unsigned int i = sc.currentPos;
      while( i && styler[i - 1] == '\\' ) {
        i--;
      }
      if( ( sc.currentPos - i ) % 2 == 1 ) {
        continue;
      }
      sc.ForwardSetState( lastStateS );
    }
    if( sc.state == SCE_CSS_OPERATOR ) {
      if( op == ' ' ) {
        unsigned int i = startPos;
        op = styler.SafeGetCharAt( i - 1 );
        opPrev = styler.SafeGetCharAt( i - 2 );
        while( --i ) {
          lastState = styler.StyleAt( i - 1 );
          if( lastState != SCE_CSS_OPERATOR && lastState != SCE_CSS_COMMENT ) {
            break;
          }
        }
      }
      switch( op ) {
        case '@':
          if( lastState == SCE_CSS_DEFAULT || hasNesting ) {
            sc.SetState( SCE_CSS_DIRECTIVE );
          }
          break;
        case '>':
        case '+':
          if( lastState == SCE_CSS_TAG || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
              lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_DEFAULT );
          }
          break;
        case '[':
          if( lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
              lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_ATTRIBUTE );
          }
          break;
        case ']':
          if( lastState == SCE_CSS_ATTRIBUTE ) {
            sc.SetState( SCE_CSS_TAG );
          }
          break;
        case '{':
          nestingLevel++;
          switch( lastState ) {
            case SCE_CSS_MEDIA:
              sc.SetState( SCE_CSS_DEFAULT );
              break;
            case SCE_CSS_TAG:
            case SCE_CSS_DIRECTIVE:
              sc.SetState( SCE_CSS_IDENTIFIER );
              break;
          }
          break;
        case '}':
          if( --nestingLevel < 0 ) {
            nestingLevel = 0;
          }
          switch( lastState ) {
            case SCE_CSS_DEFAULT:
            case SCE_CSS_VALUE:
            case SCE_CSS_IMPORTANT:
            case SCE_CSS_IDENTIFIER:
            case SCE_CSS_IDENTIFIER2:
            case SCE_CSS_IDENTIFIER3:
              if( hasNesting )
              { sc.SetState( nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT ); }
              else
              { sc.SetState( SCE_CSS_DEFAULT ); }
              break;
          }
          break;
        case '(':
          if( lastState == SCE_CSS_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_TAG );
          } else if( lastState == SCE_CSS_EXTENDED_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_EXTENDED_PSEUDOCLASS );
          }
          break;
        case ')':
          if( lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
              lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
              lastState == SCE_CSS_PSEUDOELEMENT || lastState == SCE_CSS_EXTENDED_PSEUDOELEMENT ) {
            sc.SetState( SCE_CSS_TAG );
          }
          break;
        case ':':
          switch( lastState ) {
            case SCE_CSS_TAG:
            case SCE_CSS_DEFAULT:
            case SCE_CSS_CLASS:
            case SCE_CSS_ID:
            case SCE_CSS_PSEUDOCLASS:
            case SCE_CSS_EXTENDED_PSEUDOCLASS:
            case SCE_CSS_UNKNOWN_PSEUDOCLASS:
            case SCE_CSS_PSEUDOELEMENT:
            case SCE_CSS_EXTENDED_PSEUDOELEMENT:
              sc.SetState( SCE_CSS_PSEUDOCLASS );
              break;
            case SCE_CSS_IDENTIFIER:
            case SCE_CSS_IDENTIFIER2:
            case SCE_CSS_IDENTIFIER3:
            case SCE_CSS_EXTENDED_IDENTIFIER:
            case SCE_CSS_UNKNOWN_IDENTIFIER:
            case SCE_CSS_VARIABLE:
              sc.SetState( SCE_CSS_VALUE );
              lastStateVal = lastState;
              break;
          }
          break;
        case '.':
          if( lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
              lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_CLASS );
          }
          break;
        case '#':
          if( lastState == SCE_CSS_TAG || lastState == SCE_CSS_DEFAULT || lastState == SCE_CSS_CLASS || lastState == SCE_CSS_ID ||
              lastState == SCE_CSS_PSEUDOCLASS || lastState == SCE_CSS_EXTENDED_PSEUDOCLASS || lastState == SCE_CSS_UNKNOWN_PSEUDOCLASS ) {
            sc.SetState( SCE_CSS_ID );
          }
          break;
        case ',':
        case '|':
        case '~':
          if( lastState == SCE_CSS_TAG ) {
            sc.SetState( SCE_CSS_DEFAULT );
          }
          break;
        case ';':
          switch( lastState ) {
            case SCE_CSS_DIRECTIVE:
              if( hasNesting )
              { sc.SetState( nestingLevel > 0 ? SCE_CSS_IDENTIFIER : SCE_CSS_DEFAULT ); }
              else
              { sc.SetState( SCE_CSS_DEFAULT ); }
              break;
            case SCE_CSS_VALUE:
            case SCE_CSS_IMPORTANT:
              if( insideParentheses )
              { sc.SetState( lastState ); }
              else {
                if( lastStateVal == SCE_CSS_VARIABLE )
                { sc.SetState( SCE_CSS_DEFAULT ); }
                else
                { sc.SetState( SCE_CSS_IDENTIFIER ); }
              }
              break;
            case SCE_CSS_VARIABLE:
              if( lastStateVar == SCE_CSS_VALUE ) {
                if( insideParentheses )
                { sc.SetState( SCE_CSS_VALUE ); }
                else
                { sc.SetState( SCE_CSS_IDENTIFIER ); }
              } else
              { sc.SetState( SCE_CSS_DEFAULT ); }
              break;
          }
          break;
        case '!':
          if( lastState == SCE_CSS_VALUE ) {
            sc.SetState( SCE_CSS_IMPORTANT );
          }
          break;
      }
    }
    if( sc.ch == '*' && sc.state == SCE_CSS_DEFAULT ) {
      sc.SetState( SCE_CSS_TAG );
      continue;
    }
    if( sc.ch == '(' ) {
      insideParentheses = true;
    } else if( sc.ch == ')' ) {
      insideParentheses = false;
    }
    if( hasVariables ) {
      if( sc.ch == varPrefix ) {
        switch( sc.state ) {
          case SCE_CSS_DEFAULT:
            if( isLessDocument )
            { break; }
          case SCE_CSS_VALUE:
            lastStateVar = sc.state;
            sc.SetState( SCE_CSS_VARIABLE );
            continue;
        }
      }
      if( sc.state == SCE_CSS_VARIABLE ) {
        if( IsAWordChar( sc.ch ) ) {
          continue;
        }
        if( lastStateVar == SCE_CSS_VALUE ) {
          sc.SetState( SCE_CSS_VALUE );
        }
      }
      if( sc.ch == '&' ) {
        switch( sc.state ) {
          case SCE_CSS_DEFAULT:
          case SCE_CSS_IDENTIFIER:
            sc.SetState( SCE_CSS_TAG );
            continue;
        }
      }
    }
    if( hasNesting ) {
      if( sc.state == SCE_CSS_IDENTIFIER && ( IsAWordChar( sc.ch ) || sc.ch == ':' || sc.ch == '.' || sc.ch == '#' ) ) {
        unsigned int endPos = startPos + length;
        int ch;
        for( unsigned int i = sc.currentPos; i < endPos; i++ ) {
          ch = styler.SafeGetCharAt( i );
          if( ch == ';' || ch == '}' ) {
            break;
          }
          if( ch == '{' ) {
            sc.SetState( SCE_CSS_DEFAULT );
            continue;
          }
        }
      }
    }
    if( IsAWordChar( sc.ch ) ) {
      if( sc.state == SCE_CSS_DEFAULT ) {
        sc.SetState( SCE_CSS_TAG );
      }
      continue;
    }
    if( IsAWordChar( sc.chPrev ) && (
          sc.state == SCE_CSS_IDENTIFIER || sc.state == SCE_CSS_IDENTIFIER2 ||
          sc.state == SCE_CSS_IDENTIFIER3 || sc.state == SCE_CSS_EXTENDED_IDENTIFIER ||
          sc.state == SCE_CSS_UNKNOWN_IDENTIFIER ||
          sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
          sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
          sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS ||
          sc.state == SCE_CSS_IMPORTANT ||
          sc.state == SCE_CSS_DIRECTIVE
        ) ) {
      char s[100];
      sc.GetCurrentLowered( s, sizeof( s ) );
      char *s2 = s;
      while( *s2 && !IsAWordChar( *s2 ) ) {
        s2++;
      }
      switch( sc.state ) {
        case SCE_CSS_IDENTIFIER:
        case SCE_CSS_IDENTIFIER2:
        case SCE_CSS_IDENTIFIER3:
        case SCE_CSS_EXTENDED_IDENTIFIER:
        case SCE_CSS_UNKNOWN_IDENTIFIER:
          if( css1Props.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_IDENTIFIER );
          } else if( css2Props.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_IDENTIFIER2 );
          } else if( css3Props.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_IDENTIFIER3 );
          } else if( exProps.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_EXTENDED_IDENTIFIER );
          } else
          { sc.ChangeState( SCE_CSS_UNKNOWN_IDENTIFIER ); }
          break;
        case SCE_CSS_PSEUDOCLASS:
        case SCE_CSS_PSEUDOELEMENT:
        case SCE_CSS_EXTENDED_PSEUDOCLASS:
        case SCE_CSS_EXTENDED_PSEUDOELEMENT:
        case SCE_CSS_UNKNOWN_PSEUDOCLASS:
          if( op == ':' && opPrev != ':' && pseudoClasses.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_PSEUDOCLASS );
          } else if( opPrev == ':' && pseudoElements.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_PSEUDOELEMENT );
          } else if( ( op == ':' || ( op == '(' && lastState == SCE_CSS_EXTENDED_PSEUDOCLASS ) ) && opPrev != ':' && exPseudoClasses.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_EXTENDED_PSEUDOCLASS );
          } else if( opPrev == ':' && exPseudoElements.InList( s2 ) ) {
            sc.ChangeState( SCE_CSS_EXTENDED_PSEUDOELEMENT );
          } else
          { sc.ChangeState( SCE_CSS_UNKNOWN_PSEUDOCLASS ); }
          break;
        case SCE_CSS_IMPORTANT:
          if( strcmp( s2, "important" ) != 0 ) {
            sc.ChangeState( SCE_CSS_VALUE );
          }
          break;
        case SCE_CSS_DIRECTIVE:
          if( op == '@' && strcmp( s2, "media" ) == 0 ) {
            sc.ChangeState( SCE_CSS_MEDIA );
          }
          break;
      }
    }
    if( sc.ch != '.' && sc.ch != ':' && sc.ch != '#' && (
          sc.state == SCE_CSS_CLASS || sc.state == SCE_CSS_ID ||
          ( sc.ch != '(' && sc.ch != ')' && (
              sc.state == SCE_CSS_PSEUDOCLASS || sc.state == SCE_CSS_PSEUDOELEMENT ||
              sc.state == SCE_CSS_EXTENDED_PSEUDOCLASS || sc.state == SCE_CSS_EXTENDED_PSEUDOELEMENT ||
              sc.state == SCE_CSS_UNKNOWN_PSEUDOCLASS
            ) )
        ) ) {
      sc.SetState( SCE_CSS_TAG );
    }
    if( sc.Match( '/', '*' ) ) {
      lastStateC = sc.state;
      comment_mode = eCommentBlock;
      sc.SetState( SCE_CSS_COMMENT );
      sc.Forward();
    } else if( hasSingleLineComments && sc.Match( '/', '/' ) && !insideParentheses ) {
      lastStateC = sc.state;
      comment_mode = eCommentLine;
      sc.SetState( SCE_CSS_COMMENT );
      sc.Forward();
    } else if( ( sc.state == SCE_CSS_VALUE || sc.state == SCE_CSS_ATTRIBUTE )
               && ( sc.ch == '\"' || sc.ch == '\'' ) ) {
      lastStateS = sc.state;
      sc.SetState( ( sc.ch == '\"' ? SCE_CSS_DOUBLESTRING : SCE_CSS_SINGLESTRING ) );
    } else if( IsCssOperator( sc.ch )
               && ( sc.state != SCE_CSS_ATTRIBUTE || sc.ch == ']' )
               && ( sc.state != SCE_CSS_VALUE || sc.ch == ';' || sc.ch == '}' || sc.ch == '!' )
               && ( ( sc.state != SCE_CSS_DIRECTIVE && sc.state != SCE_CSS_MEDIA ) || sc.ch == ';' || sc.ch == '{' )
             ) {
      if( sc.state != SCE_CSS_OPERATOR ) {
        lastState = sc.state;
      }
      sc.SetState( SCE_CSS_OPERATOR );
      op = sc.ch;
      opPrev = sc.chPrev;
    }
  }
  sc.Complete();
}

static void FoldCSSDoc( unsigned int startPos, int length, int, WordList *[], Accessor &styler ) {
  bool foldComment = styler.GetPropertyInt( "fold.comment" ) != 0;
  bool foldCompact = styler.GetPropertyInt( "fold.compact", 1 ) != 0;
  unsigned int endPos = startPos + length;
  int visibleChars = 0;
  int lineCurrent = styler.GetLine( startPos );
  int levelPrev = styler.LevelAt( lineCurrent ) & SC_FOLDLEVELNUMBERMASK;
  int levelCurrent = levelPrev;
  char chNext = styler[startPos];
  bool inComment = ( styler.StyleAt( startPos - 1 ) == SCE_CSS_COMMENT );
  for( unsigned int i = startPos; i < endPos; i++ ) {
    char ch = chNext;
    chNext = styler.SafeGetCharAt( i + 1 );
    int style = styler.StyleAt( i );
    bool atEOL = ( ch == '\r' && chNext != '\n' ) || ( ch == '\n' );
    if( foldComment ) {
      if( !inComment && ( style == SCE_CSS_COMMENT ) ) {
        levelCurrent++;
      } else if( inComment && ( style != SCE_CSS_COMMENT ) ) {
        levelCurrent--;
      }
      inComment = ( style == SCE_CSS_COMMENT );
    }
    if( style == SCE_CSS_OPERATOR ) {
      if( ch == '{' ) {
        levelCurrent++;
      } else if( ch == '}' ) {
        levelCurrent--;
      }
    }
    if( atEOL ) {
      int lev = levelPrev;
      if( visibleChars == 0 && foldCompact ) {
        lev |= SC_FOLDLEVELWHITEFLAG;
      }
      if( ( levelCurrent > levelPrev ) && ( visibleChars > 0 ) ) {
        lev |= SC_FOLDLEVELHEADERFLAG;
      }
      if( lev != styler.LevelAt( lineCurrent ) ) {
        styler.SetLevel( lineCurrent, lev );
      }
      lineCurrent++;
      levelPrev = levelCurrent;
      visibleChars = 0;
    }
    if( !isspacechar( ch ) ) {
      visibleChars++;
    }
  }
  int flagsNext = styler.LevelAt( lineCurrent ) & ~SC_FOLDLEVELNUMBERMASK;
  styler.SetLevel( lineCurrent, levelPrev | flagsNext );
}

static const char * const cssWordListDesc[] = {
  "CSS1 Properties",
  "Pseudo-classes",
  "CSS2 Properties",
  "CSS3 Properties",
  "Pseudo-elements",
  "Browser-Specific CSS Properties",
  "Browser-Specific Pseudo-classes",
  "Browser-Specific Pseudo-elements",
  0
};

LexerModule lmCss( SCLEX_CSS, ColouriseCssDoc, "css", FoldCSSDoc, cssWordListDesc );
